1 /*
2  * This file is part of gtkD.
3  *
4  * gtkD is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License
6  * as published by the Free Software Foundation; either version 3
7  * of the License, or (at your option) any later version, with
8  * some exceptions, please read the COPYING file.
9  *
10  * gtkD is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with gtkD; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
18  */
19 module linker.Loader;
20 
21 import std.algorithm : canFind;
22 import std.stdio;
23 import std.string;
24 
25 import linker.Exception;
26 
27 public class Linker {
28     /** Contain lib handler by name */
29     private static void * [string] loadedLibs;
30     /** Contain failed symbols loads by lib */
31     private static string[][string] failedLoads;
32 
33     /** 
34      * Function for catching unsopported symbols
35      */
36     extern(C) static void unsupportedSymbol() {
37         throw new LinkException("The function you are calling is not pressent in your version of lib");
38     }
39 
40     /** 
41      * Link the provided symbol
42      * Params:
43      *   func = The function we are linking
44      *   symbol = Symbol for linking
45      *   libs = Libraries for symbol searching
46      */
47     public static void link(T)(ref T func, string symbol, const string [] libs ...) {
48         func = cast(T)getSymbol(symbol, libs);
49     }
50 
51     /** 
52      * Get the symbol from the lib
53      * Params:
54      *   symbol = Symbol for import
55      *   libs = Libraries for symbol searching
56      * Returns: Handle for the symbol
57      */
58     public static void * getSymbol(string symbol, const string [] libs ...) {
59         void * handle = null;
60 
61         foreach (lib; libs) {
62             if (lib !in loadedLibs) loadLibrary(lib);
63 
64             handle = getSymbolOS(loadedLibs[lib], symbol);
65 
66             if (handle !is null) break;
67         }
68 
69         if (handle is null) {
70             foreach (lib; libs) {
71                 failedLoads[lib] ~= symbol;
72             }
73             handle = &unsupportedSymbol;
74         }
75 
76         return handle;
77     }
78 
79     /** 
80      * Load a dynamic lib
81      * Params:
82      *   library = Library for loading
83      */
84     public static void loadLibrary(string library) {
85         import std.algorithm.searching : canFind;
86         import std.string : split;
87 
88         void * handle = null;
89 
90         if (library.canFind(';')) {
91             foreach (lib; library.split(';')) {
92                 handle = loadLibraryOS(lib);
93                 if (handle !is null) break;
94             }
95         }
96         else handle = loadLibraryOS(library);
97 
98         if (handle is null) throw new LinkException("Library load failed ("~ library ~"): "~ getLastErrorMessageOS());
99     
100         loadedLibs[library] = handle;
101     }
102 
103     /** 
104      * Unload a library
105      * Params:
106      *   library = Library for unloading
107      */
108     public static void unloadLibrary(string library) {
109         unloadLibraryOS(loadedLibs[library]);
110         loadedLibs.remove(library);
111     }
112 
113     /** 
114      * Check load state
115      * Returns: True if was load fails
116      */
117     public static bool isFails() {
118         return failedLoads.length != 0;
119     }
120 
121     /** 
122      * Getter for loaded libs
123      * Returns: Loaded libs list
124      */
125     public static string [] getLoaded() {
126         return loadedLibs.keys;
127     }
128 
129     /** 
130      * Check for lib loading
131      * Params:
132      *   lib = Lib for checking
133      * Returns: true if the lib was loaded
134      */
135     public static bool isLoaded(string lib) {
136         if(lib in loadedLibs) return true;
137         return false;
138     }
139 
140     /** 
141      * Check failed library loads
142      * Params:
143      *   lib = Lib for checking
144      * Returns: Failed loads for the library
145      */
146     public static string [] getFails(string lib) {
147         if (lib in failedLoads) return failedLoads[lib];
148         return null;
149     }
150 
151     /**
152      * Unload all loaded libs at exit
153      */
154     static ~this() {
155         foreach(lib; loadedLibs.keys) {
156             unloadLibrary(lib);
157         }
158     }
159 
160     /** Functions for platform specific library load */
161     version (Windows) {
162         /** Specific imports */
163         import core.sys.windows.winnt : IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386;
164         import core.sys.windows.winbase : LoadLibraryA, GetProcAddress, FreeLibrary;
165 
166         /** 
167          * Load library by using OS-specific functions
168          * Params:
169          *   library = Library's for loading name
170          * Returns: Loaded library if everything ok or null in other cases
171          */
172         private static void * loadLibraryOS(string library) {
173             setDLLPath();
174             return LoadLibraryA(cast(char *)toStringz(library));
175         }
176 
177         /** 
178          * Get symbol from library by using OS-specific functions
179          * Params:
180          *   handle = A handle to the DLL module that contains the function or variable
181          *   symbol = The function or variable name, or the function's ordinal value
182          * Returns: Address of symbol or null
183          */
184         private static void * getSymbolOS(void * handle, string symbol) {
185             return GetProcAddress(handle, cast(char *)toStringz(symbol));
186         }
187 
188         /** 
189          * Unload library by using OS-specific functions
190          * Params:
191          *   lib = Library for unloading
192          * Returns: True if everything is ok
193          */
194         private static bool unloadLibraryOS(void * lib) {
195             return cast(bool)FreeLibrary(lib);
196         }
197 
198         /** 
199          * Get the last error message by using OS-specific functions
200          * Returns: String with error message or nothing
201          */
202         private static string getLastErrorMessageOS() {
203             import core.sys.windows.winbase : GetLastError, FormatMessageA, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_ARGUMENT_ARRAY;
204             import core.sys.windows.winnt : LANG_NEUTRAL;
205             import core.stdc.string : memset;
206 
207             char [] buffer = new char[2048];
208             memset(buffer.ptr, '\0', 2048);
209         
210             FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
211                 null, GetLastError(), LANG_NEUTRAL, buffer.ptr, 2048, cast(char **)["\0".ptr].ptr);
212 
213             return fromStringz(buffer.ptr).idup;
214         }
215 
216         /** 
217          * Say Windows where it should search dll
218          */
219         private static void setDLLPath() {
220             static bool is_set = false;
221 
222             if (is_set) return;
223 
224             string gtk_path = findGtkLibs();
225 
226             if (gtk_path !is null) {
227                 SetDllDirectoryA((gtk_path ~ '\0').ptr);
228             }
229 
230             is_set = true;
231         }
232 
233         /** 
234          * Find Gtk libs on the computer
235          * Returns: Path to the Gtk libs or null
236          */
237         private static string findGtkLibs() {
238             import std.algorithm.iteration : splitter;
239             import std.process : environment;
240             import std.path : buildNormalizedPath;
241             import std.file : exists;
242 
243             foreach (lib; [`libgtk-3-0.dll`, `libgtk-4-1.dll`, `gtk-3.dll`, `gtk-4.dll`]) {
244                 foreach (path; splitter(environment.get("PATH"), ';')) {
245                     string dll_path = buildNormalizedPath(path, lib);   
246                 
247                     if (exists(dll_path) == false) continue;
248                     if (checkDLLArch(dll_path)) return path;
249                 }
250             }
251 
252             return null;
253         }
254 
255         /** 
256          * Check a DLL for compatible with the machine
257          * Params:
258          *   dll_path = Path to the dll for check
259          * Returns: true if the dll is compatible with the machine
260          */
261         private static bool checkDLLArch(string dll_path) {
262             import std.stdio : File;
263 
264             File dll = File(dll_path);
265 
266             dll.seek(0xc3);
267             dll.seek(cast(int)dll.rawRead(new int[1])[0]);
268 
269             if (cast(uint)dll.rawRead(new uint[1])[0] != 0x00004550) return false;
270 
271             ushort win_type = cast(ushort)dll.rawRead(new ushort[1])[0];
272 
273             version(Win32) {
274                 return win_type == IMAGE_FILE_MACHINE_I386;
275             }
276             version(Win64) {
277                 return win_type == IMAGE_FILE_MACHINE_AMD64;
278             }
279         }
280     } 
281     else {
282         /** Import OS-specific libs */
283         import core.sys.posix.dlfcn : dlopen, dlerror, dlsym, dlclose;
284         import core.sys.posix.dlfcn : RTLD_NOW, RTLD_GLOBAL;
285 	    
286         import std.path : buildPath;
287 
288         /** String for containing last error message */
289         private static string last_error = null;
290 
291         version(OSX) {
292             /** 
293              * Find the path to the libs on MacOS
294              * Returns: Path to the libs in the MacOS
295              */
296             private static string getBasePath() {
297                 import std.process : environment;
298                 import std.path : buildPath;
299 
300                 static string path = null;
301 
302                 if (path !is null) return path;
303 
304                 path = environment.get("GTK_BASEPATH");
305                 if (path is null) {
306                     path = environment.get("HOMEBREW_ROOT");
307                     if (path !is null) path = buildPath(path, "lib");
308                 }
309                 
310                 return path;
311             }
312 
313             private static string base_path = getBasePath();
314         }
315         else {
316             private static string base_path = "";
317         }
318 
319         /** 
320          * Load library by using OS-specific functions
321          * Params:
322          *   library = Library's for loading name
323          * Returns: Loaded library if everything ok or null in other cases
324          */
325         private static void * loadLibraryOS(string library) {
326             import std.path : buildPath;
327 
328             void * handle = dlopen(cast(char *)toStringz(buildPath(base_path, library)), RTLD_GLOBAL | RTLD_NOW);
329         
330             if (!handle) last_error = fromStringz(dlerror()).idup;
331 
332             return handle;
333         }
334 
335         /** 
336          * Get symbol from library by using OS-specific functions
337          * Params:
338          *   handle = A handle to the DLL module that contains the function or variable
339          *   symbol = The function or variable name, or the function's ordinal value
340          * Returns: Address of symbol or null
341          */
342         private static void * getSymbolOS(void * handle, string symbol) {
343             void * symbol_handle = dlsym(handle, cast(char *)toStringz(symbol));
344             
345             if (!symbol_handle) last_error = fromStringz(dlerror()).idup;
346 
347             return symbol_handle;
348         }
349 
350         /** 
351          * Unload library by using OS-specific functions
352          * Params:
353          *   lib = Library for unloading
354          * Returns: True if everything is ok
355          */
356         private static bool unloadLibraryOS(void * lib) {
357             int res = dlclose(lib);
358             if (res != 0) last_error = fromStringz(dlerror()).idup;
359             return res == 0;
360         }
361 
362         /** 
363          * Get the last error message by using OS-specific functions
364          * Returns: String with error message or nothing
365          */
366         private static string getLastErrorMessageOS() {
367             scope(exit) last_error = null;
368             return last_error;
369         }
370     }
371 }